﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Diagnostics;

namespace Homework2
{
    public class QueryParser
    {
        private List<WebNode> _pageNodes;
        private readonly Dictionary<string, List<WebNode>> _nodesDict = new Dictionary<string, List<WebNode>>();
        private readonly List<Tuple<string, string, string>> _whereStatements = new List<Tuple<string, string, string>>(); 

        private string _select;
        private string _from;
        private string _where;

        private static readonly Regex InRegex = new Regex(@"((.+)[iI][nN](.+)(,))|((.+)[iI][nN](.+)$)");
        private static readonly Regex AndRegex = new Regex(@"((^|.+?)(([aA][nN][dD])|(\Z)))");

        private List<WebNode> PerformOps(string valueString, List<WebNode> startNodes)
        {
            if (startNodes == null || startNodes.Count == 0)
            {
                return new List<WebNode>();
            }

            //TODO: Deal with /?
            //TODO: Deal with +?

            var finalNodes = new List<WebNode>(startNodes);

            foreach (var ch in valueString)
            {
                switch (ch)
                {
                    case '\'':
                        finalNodes = finalNodes.Prime(true);
                        break;

                    case '&':
                        finalNodes = finalNodes.Head();
                        break;

                    case '!':
                        finalNodes = finalNodes.Tail();
                        break;
                }
            }

            return finalNodes;
        }

        private List<WebNode> ParseSelect()
        {
            var lowerSelect = _select.ToLower();
            if (lowerSelect.Contains("as"))
            {
                var asIndex = lowerSelect.IndexOf("as");
                var beforeAs = _select.Substring(0, asIndex).Trim();
                var afterAs = _select.Substring(asIndex + 2).Trim();

                var returnNodes = new List<WebNode>();
                if (!_select.Contains('[')) // Something like  x' as x.Group
                {
                    var afterAsVariable = afterAs.Substring(0, 1);
                    var afterAsNodes = PerformOps(afterAs, _nodesDict[afterAsVariable]);
                    var afterAsProp = afterAs.Substring(afterAs.IndexOf('.') + 1);

                    var beforeAsVariable = beforeAs.Substring(0, 1);
                    var beforeAsNodes = PerformOps(beforeAs, _nodesDict[beforeAsVariable]);

                    for (var i = 0; i < afterAsNodes.Count; i++)
                    {
                        var newNode = new WebNode(null, null) { Url = afterAsNodes[i].Peek(afterAsProp) };

                        if (i < beforeAsNodes.Count)
                        {
                            newNode.Children.Add(beforeAsNodes[i]);
                        }
                        returnNodes.Add(newNode);
                    }
                }

                return returnNodes;
            }
            else if (!_select.Contains(".")) // Something like  x   or  x!   ...etc.
            {
                var variable = _select.Substring(0, 1);

                List<WebNode> nodes;
                if (_nodesDict.TryGetValue(variable, out nodes))
                {
                    var finalNodes = PerformOps(_select, nodes);
                    if (finalNodes.Count > 1)
                    {
                        finalNodes = Oql.Hang(null, null, null, null, null, finalNodes);
                    }
                    return finalNodes;
                }
            }
            return null;
        }


        //For now I am assuming that the first thing is the x in "webpage"
        //Anything else after that can be off of that
        private void ParseFrom()
        {

            var args = InRegex.Matches(_from);
            Debug.WriteLine("in list");

            foreach (Match inArg in args)
            {
                var temp = inArg.Value;

                var inIndex = temp.IndexOf("in", StringComparison.InvariantCultureIgnoreCase);

                var newVariable = temp.Remove(inIndex).Trim(); // Part before the 'in'
                var newValue = temp.Remove(0, inIndex + 2).Trim(); // Part after the 'in'

                Debug.WriteLine(temp);
                Debug.WriteLine(newVariable);
                Debug.WriteLine(newValue);

                if (newVariable.Length != 1)
                {
                    Debug.WriteLine("*** WEIRD VARIABLE!?: {0}", newVariable);
                }
                else
                {
                    if (newValue.StartsWith("browse", StringComparison.InvariantCultureIgnoreCase))
                    {
                        var webpage = newValue.Substring(newValue.IndexOf('(') + 1).TrimEnd(')');
                        var nodes = PageParser.ParsePage(webpage);
                        _nodesDict.Add(newVariable, nodes);
                    }
                    else
                    {
                        // Check if we already have a tree for any part of the value
                        int i;
                        for (i = newValue.Length; i > 0; i--)
                        {
                            var str = newValue.Substring(0, i);
                            
                            List<WebNode> nodes;
                            if (_nodesDict.TryGetValue(str, out nodes))
                            {
                                var opsToPerform = newValue.Replace(str, string.Empty);

                                // No WHERE applicable to this variable
                                if (!_whereStatements.Any(t => t.Item1.Equals(newVariable, StringComparison.InvariantCultureIgnoreCase)))
                                {
                                    _nodesDict.Add(newVariable, PerformOps(opsToPerform, nodes));
                                }
                                else // Have a WHERE applicable to this variable
                                {
                                    var wheres = _whereStatements.Where(t => t.Item1.Equals(newVariable, StringComparison.InvariantCultureIgnoreCase));
                                    var nodesAfterOps = PerformOps(opsToPerform, nodes);

                                    PareDownUsingWhere(nodesAfterOps, wheres);
                                    _nodesDict.Add(newVariable, nodesAfterOps);
                                }
                                break;
                            }
                        }

                        if (i == 0) // Didn't find an existing subtree for any part...
                        {
                            // Assuming alue part is something like "doc", "csPapers", "doc!", etc...

                            // No WHERE applicable to this variable
                            if (!_whereStatements.Any(t => t.Item1.Equals(newVariable, StringComparison.InvariantCultureIgnoreCase)))
                            {
                                _nodesDict.Add(newVariable, PerformOps(newValue, _pageNodes));
                            }
                            else // Have a WHERE applicable to this variable
                            {
                                var wheres = _whereStatements.Where(t => t.Item1.Equals(newVariable, StringComparison.InvariantCultureIgnoreCase));
                                var nodesAfterOps = PerformOps(newValue, _pageNodes);

                                PareDownUsingWhere(nodesAfterOps, wheres);
                                _nodesDict.Add(newVariable, nodesAfterOps);
                            }
                        }
                    }
                }
            }
        }

        private void PareDownUsingWhere(List<WebNode> nodes, IEnumerable<Tuple<string, string, string>> wheres)
        {
            var newNodes = new List<WebNode>(nodes);
            foreach (var node in newNodes)
            {
                foreach (var where in wheres)
                {
                    var checkThis = string.IsNullOrWhiteSpace(where.Item2)
                                        ? node
                                        : PerformOps(where.Item2, nodes).FirstOrDefault();
                    if (checkThis == null)
                    {
                        nodes.Remove(node);
                        break;
                    }
                    else
                    {
                        var check = where.Item3;
                        var propName = check.Substring(0, check.IndexOf(' '));
                        var doEquals = check.Contains("="); // Assuming either use of = or ~
                        var checkValue = check.Substring(check.IndexOf('"') + 1).TrimEnd('"');

                        var propValue = checkThis.Peek(propName);

                        if (propValue == null
                            || (doEquals && !propValue.Equals(checkValue))
                            || (!doEquals && !propValue.Contains(checkValue)))
                        {
                            nodes.Remove(node);
                            break;
                        }
                    }
                }
            }
        }

        private void ParseWhere()
        {
            var matches = AndRegex.Matches(_where);
            foreach (Match match in matches)
            {
                var statement = match.Value;
                statement = statement.Trim();

                if (statement.EndsWith("and", StringComparison.InvariantCultureIgnoreCase))
                {
                    statement = statement.Remove(statement.IndexOf("and", StringComparison.InvariantCultureIgnoreCase)).Trim();
                }

                var statementArg = statement.Substring(0, statement.IndexOf('.'));

                // Just assuming the where variables are only a single character...bleh
                var argOps = statementArg.Substring(1);
                statementArg = statementArg.Substring(0, 1);

                var statementCheck = statement.Substring(statement.IndexOf('.') + 1);

                _whereStatements.Add(new Tuple<string, string, string>(statementArg, argOps, statementCheck));
            }
        }

        public List<WebNode> Parser(string query, List<WebNode> pageNodes)
        {
            // Return immediately if the query is null/empty, or pageNodes is null/empty and the query does not have a 'browse' statement
            if (string.IsNullOrWhiteSpace(query) || 
                ((pageNodes == null || pageNodes.Count == 0) && query.IndexOf("browse", StringComparison.InvariantCultureIgnoreCase) < 0))
            {
                return null;
            }

            _pageNodes = pageNodes;

            var fromIndex = query.IndexOf("from", StringComparison.InvariantCultureIgnoreCase) + 5; // +5 to skip over the 'from' word
            var whereIndex = query.IndexOf("where", StringComparison.InvariantCultureIgnoreCase);
            if (whereIndex > 0)
            {
                whereIndex += 6; // Skip over the 'where' word
            }

            _select = query.Trim().Substring(7, fromIndex - 5 - 7).Trim();
            _from = query.Substring(fromIndex, whereIndex > 0
                                                   ? whereIndex - fromIndex
                                                   : query.Length - fromIndex).Trim();
            _where = whereIndex > 0
                         ? query.Substring(whereIndex, query.Length - whereIndex).Trim()
                         : null;

            Debug.WriteLine("Where Statement");
            if (_where != null)
            {
                Debug.WriteLine(_where);
                ParseWhere();
            }
            else
            {
                Debug.WriteLine("NO WHERE STATEMENT");
            }

            Debug.WriteLine("From Statement");
            Debug.WriteLine(_from);
            ParseFrom();

            Debug.WriteLine("Select Statement");
            Debug.WriteLine(_select);
            return ParseSelect();
        }
    }
}
